/*
 * Team Huskies:
 * Aakanksha Gaur,
 * Alice Robinson,
 * Paul Winters,
 * Steve Bradshaw
 * Date: June 8th 2011
 */
#include <stdlib.h>
#include <time.h>
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include "queue.h"
#include "process.h"


#define DISK_REQ 0
#define VID_REQ 1
#define KEY_REQ 2
#define MUTEX 3
#define SIGNAL_WAIT 4
#define SIGNAL 5
#define IDLE 6

#define TIMER_INTERRUPT 7
#define KEYBOARD_INTERRUPT 8
#define DISK_INTERRUPT 9
#define VIDEO_INTERRUPT 10

#define MAX 10
#define TIMER_DELAY 100
int bufavail = MAX;
int interrupt_buf[MAX];
int tail;
int head;
int request_id = 0;

PCB_str *my_running_process;
PCB_str *interrupted_process;
int no_of_processes; //used for the idle process

int cpu_cycles = 0;

//Queues & PCB list
static queue *readyQueue;
static queue *diskIOQueue;
static queue *videoIOQueue;
static queue *keyboardIOQueue;
static queue *interruptQueue;
static queue *semiphore_queue;
static int sharedData;

static PCB_str pcb_list[20];

static pthread_attr_t attr;	  // Used for Joining
static pthread_attr_t attr_detach;

static pthread_mutex_t mutex;    // mutual exclusion lock
static pthread_mutex_t qlock;	  // used to ensure something is queued first

static pthread_cond_t buf_not_full;	  // signaling when a Q is busy
static pthread_cond_t buf_not_empty;		  // ensure a queue has an element
pthread_t videopth;
pthread_t diskpth;
pthread_t keypth;




void *keyboardInterrupt(void *arg) {
	printf( "\nSTART KEYBOARD INTERRUPT TIMER!");
	int the_keyboard_interrupt_int = (rand() % 10000000);
	int x = 0;
	for (x = 0; x < the_keyboard_interrupt_int; x++) {
		printf(" "); //add a tiny bit more delay
	}
	//sleep(1);
	pthread_mutex_lock(&qlock);
	if(bufavail == 0) {
		pthread_cond_wait(&buf_not_full, &qlock);
	}
	pthread_mutex_unlock(&qlock);

	q_enqueue(interruptQueue, (qelem_t) KEYBOARD_INTERRUPT);

	pthread_mutex_lock(&qlock);
	bufavail = bufavail - 1;
	pthread_cond_signal(&buf_not_empty);
	pthread_mutex_unlock(&qlock);
	pthread_exit(NULL);
	return NULL;
}

void *diskInterrupt(void *arg) {
	printf( "\nSTART DISK INTERRUPT TIMER!");
	int x = 0;
	int the_disk_interrupt_int = (rand() % 10000000);
	for (x = 0; x < the_disk_interrupt_int; x++)
	{
		printf(""); //add a tiny bit more delay
	}
	//sleep(1);
	//printf( "DISK INTERRUPT SIGNAL OCCURED! \n");
	pthread_mutex_lock(&qlock);
	if(bufavail == 0) {
		pthread_cond_wait(&buf_not_full, &qlock);
	}
	pthread_mutex_unlock(&qlock);

	q_enqueue(interruptQueue, (qelem_t) DISK_INTERRUPT);
	//interrupt_buf[tail % MAX] = DISK_INTERRUPT;
	//tail = tail + 1;

	pthread_mutex_lock(&qlock);
	bufavail = bufavail - 1;
	pthread_cond_signal(&buf_not_empty);
	pthread_mutex_unlock(&qlock);
	pthread_exit(NULL);
	return NULL;
}

void *videoInterrupt(void *arg)
{
	printf( "\nSTART VIDEO INTERRUPT TIMER!");
	int x = 0;
	int the_video_interrupt_int = (rand() % 10000000);
	for (x = 0; x < the_video_interrupt_int; x++)
	{
		printf(""); //add a tiny bit more delay
	}
	//sleep(1);

	pthread_mutex_lock(&qlock);
	//If buffer full
	if(bufavail == 0) {
		pthread_cond_wait(&buf_not_full, &qlock);
	}
	pthread_mutex_unlock(&qlock);

	q_enqueue(interruptQueue, (qelem_t) VIDEO_INTERRUPT);


	pthread_mutex_lock(&qlock);
	bufavail = bufavail - 1;
	pthread_cond_signal(&buf_not_empty);
	pthread_mutex_unlock(&qlock);
	pthread_exit(NULL);
	return NULL;
}

void cpuCounter(/*void *arg*/) {

	int p_counter = 0;
	int running = 1; 	//used for endless loop
	//Which interrupt request
	int interrupt_id = 0;
	int timer_interrupt_counter = TIMER_DELAY;
	int cycles = 0;

    //MAIN LOOP
	while (running == 1) {//running == 1) {	/*p_counter < cpu_cycles) */
		//nanosleep(&req, (struct timespec *)NULL);
		//sleep(1);
		printf( "%s%i%s%i", ":", p_counter, " Total cycles: ", cycles);
		//printf( " ");

		interrupt_id = 0;

		//Priorit if a timer interrupt is thrown
		if(timer_interrupt_counter <= 0) { //count down done

			interrupt_id = TIMER_INTERRUPT;
			timer_interrupt_counter = TIMER_DELAY; //reset delay
		}

		if (interruptQueue->size != 0) {/*q_isempty(interruptQueue)) {*/
			//if(bufavail != MAX) {

				pthread_mutex_lock(&qlock);
				//If empty
				if(bufavail == MAX) {//If buffer has room
					pthread_cond_wait(&buf_not_empty, &qlock);
				}
				pthread_mutex_unlock(&qlock);

				printf("\nInterruptQ Size is: %i", interruptQueue->size);
				interrupt_id = (int)q_dequeue(interruptQueue);
				printf( "\nInterrupt ID is: %i", interrupt_id); //testing


				pthread_mutex_lock(&qlock);
				bufavail = bufavail + 1;
				pthread_cond_signal(&buf_not_full);
				pthread_mutex_unlock(&qlock);
			//}
		}

		//our interrupt handler
		if(interrupt_id != 0) {			switch(interrupt_id) {
				case TIMER_INTERRUPT:
					//context switch for next process on readyQ, save PC
					printf("\nVIDEO_Q size is: %i", videoIOQueue->size);
					printf("\nDISK_Q size is: %i", diskIOQueue->size);
					printf( "\n\n%s%i", "Cycle: ", p_counter);
					printf("\nThe PID before the timer interrupt:  %i", my_running_process->pid);
					my_running_process->next_step = p_counter+1;

					//scheduler(TIMER_INTERRUPT);
					interrupted_process = my_running_process;
					//load next process from readyQ
					qelem_t elem1;
					if (q_isempty(readyQueue) != 1) {
						elem1 = q_dequeue(readyQueue);
						my_running_process = elem1;
						//printf("%i", my_running_process->next_step);
					}
					//context switch
					q_enqueue(readyQueue, (qelem_t) &pcb_list[interrupted_process->pid]);
					printf("\nReadyQ size: %i", readyQueue->size);
					printf("\nContext Switch!");

					printf("\nThe PID after the timer interrupt is: %i\n", my_running_process->pid);
					p_counter = my_running_process->next_step;
					//j = my_running_process->request_no;
					break;
				case KEYBOARD_INTERRUPT:
					printf( "\n%s%i", "Cycle: ", p_counter);
					printf("\nKEYBOARD INTERRUPTER during PID %i", my_running_process->pid);

					qelem_t elem2;
					if (q_isempty(keyboardIOQueue) != 1) { //just in case it is empty
						elem2 = q_dequeue(keyboardIOQueue);
						PCB_str *keytmp = elem2;
						q_enqueue(readyQueue, (qelem_t) elem2);
						printf("\nPID %i is being put on readyQ", keytmp->pid);
						printf("\nReadyQ size: %i\n", readyQueue->size);
					}
					printf("\nDISK_Q size is: %i", keyboardIOQueue->size);
					if(keyboardIOQueue->size > 0) {
						pthread_create(&keypth, &attr_detach, keyboardInterrupt, "I'm an arg.");
						//pthread_join(keypth, NULL);
						pthread_detach(diskpth);
					}
					printf("\nKEYBOARD INTERRUPTER Complete\n");
					break;
				case DISK_INTERRUPT:
					printf( "\n%s%i", "Cycle: ", p_counter);
					printf("\nDISK INTERRUPTER during PID %i", my_running_process->pid);
					qelem_t elem3;
					if (q_isempty(diskIOQueue) != 1) { //just in case it is empty
						elem3 = q_dequeue(diskIOQueue);
						PCB_str *dsktmp = elem3;
						q_enqueue(readyQueue, (qelem_t) elem3);
						printf("\nPID %i is being put on readyQ", dsktmp->pid);
						printf("\nReadyQ size: %i\n", readyQueue->size);

					}
					printf("\nDISK_Q size is: %i", diskIOQueue->size);
					if(diskIOQueue->size > 0) {
						pthread_create(&diskpth, &attr, diskInterrupt, "I'm an arg.");
						pthread_join(diskpth, NULL);
						//pthread_detach(diskpth);
					}
					printf("\nDISK INTERRUPTER Complete\n");
					break;
				case VIDEO_INTERRUPT:
					//my_running_process->next_step = p_counter+1; //not needed just a Q move
					//scheduler(VIDEO_INTERRUPT);
					printf( "\n%s%i", "Cycle: ", p_counter);
					printf("\nVIDEO INTERRUPTER during PID %i", my_running_process->pid);
					qelem_t elem4;
					if (q_isempty(videoIOQueue) != 1) { //just in case it is empty
						elem4 = q_dequeue(videoIOQueue);
						PCB_str *vidtmp = elem4;
						q_enqueue(readyQueue, (qelem_t) elem4);
						printf("\nPID %i is being put on readyQ", vidtmp->pid);
						printf("\nReadyQ size is: %i", readyQueue->size);
						//printf("SAVING next step: %i", my_running_process->next_step);
					}
					printf("\nVIDEO_Q size is: %i", videoIOQueue->size);
					if(videoIOQueue->size > 0) {
						pthread_create(&videopth, &attr, videoInterrupt, "I'm an arg.");
						pthread_join(videopth, NULL);
						//pthread_detach(videopth);
					}
					printf("\nVIDEO INTERRUPTER Complete\n");
					break;
				default:
					printf("\nNot sure of INTERRUPT.\n");
			}//end switch
		//end if(interrupt_id != 0)
		}//if(q_isempty(interruptQueue)
		int temp_req_num = 0;
		request_id= 0;
		int req_num = 0;
		int j;
		for(j = 0; j < my_running_process->requests_size; j++) {
			temp_req_num = my_running_process->my_process->array[j][0];
			if(p_counter == temp_req_num)
			{
				req_num = temp_req_num;
				request_id = my_running_process->my_process->array[j][1];
				//printf("REQUEST ID: %i", my_running_process->my_process->array[j][1]);
			}
		}

		//INTERRUPT REQUEST HANDLER
		if(p_counter == req_num) {

			switch(request_id) {
			//printf("REQUEST ID2: %i", request_id);
				case DISK_REQ:
					printf( "\n%s%i", "Cycle: ", p_counter);
					printf("\nPID before DISK_REQ: %i", my_running_process->pid);
					my_running_process->next_step = p_counter+1;

					//scheduler(DISK_REQ);
					q_enqueue(diskIOQueue, (qelem_t) &pcb_list[my_running_process->pid]);
					printf("\nDISK_Q size is: %i", diskIOQueue->size);

					//if the ready quue is empty, put an idle process in until something loads
					if(readyQueue->size == 1) {
						//size+1 = idle process
						q_enqueue(readyQueue, (qelem_t) &pcb_list[no_of_processes + 1]);
					}

					qelem_t elem6;
					if (q_isempty(readyQueue) != 1) {
						elem6 = q_dequeue(readyQueue);
						my_running_process = elem6;
						printf("\nReadyQ size is: %i", readyQueue->size);
						p_counter = my_running_process->next_step;  //should be new proc+;
						//j = my_running_process->request_no;
						//printf("%i", my_running_process->next_step);
					}
					if(diskIOQueue->size == 1) {
						pthread_create(&diskpth, &attr, diskInterrupt, "I'm an arg.");
						pthread_join(diskpth, NULL);
						//pthread_detach(diskpth);
					}
					printf("\nPID after DISK_REQ %i\n\n", my_running_process->pid);
					break;
			case VID_REQ:
				printf( "\n%s%i", "Cycle: ", p_counter);
				printf("\nPID before VID_REQ is: %i", my_running_process->pid);
				my_running_process->next_step = p_counter+1;

					//scheduler(VID_REQ);
				q_enqueue(videoIOQueue, (qelem_t) &pcb_list[my_running_process->pid]);
				printf("\nVIDEO_Q size is: %i", videoIOQueue->size);

				//if the ready quue is empty, put an idle process in until something loads
				if(q_isempty(readyQueue) != 1) {
					//size+1 = idle process
					q_enqueue(readyQueue, (qelem_t) &pcb_list[no_of_processes + 1]);
				}

				qelem_t elem5;
				if (q_isempty(readyQueue) != 1) {
					elem5 = q_dequeue(readyQueue);
					//PCB_str *pcbvidtmp = elem5;
					my_running_process = elem5;
					printf("\nReadyQ size is: %i", readyQueue->size);
					p_counter = my_running_process->next_step;  //should be new process cnt;
					//j = my_running_process->request_no;
					//printf("%i", my_running_process->next_step);
				}

				if(videoIOQueue->size == 1) {
					pthread_create(&videopth, &attr_detach, videoInterrupt, "I'm an arg.");
					//pthread_join(videopth, NULL);
					pthread_detach(videopth);
				}
				printf("\nPID after VID_REQ: %i\n\n", my_running_process->pid);
				break;

			case KEY_REQ:
				printf( "\n%s%i", "Cycle: ", p_counter);
				printf("\nPID before KEY_REQ %i", my_running_process->pid);
				my_running_process->next_step = p_counter+1;

				//scheduler(KEY_REQ);
				q_enqueue(keyboardIOQueue, (qelem_t) &pcb_list[my_running_process->pid]);
				printf("\nDISK_Q size is: %i", diskIOQueue->size);
				//if the ready quue is empty, put an idle process in until something loads
				if(readyQueue->size == 1) {
					//size+1 = idle process
					q_enqueue(readyQueue, (qelem_t) &pcb_list[no_of_processes + 1]);
				}
				qelem_t elem7;
				if (q_isempty(readyQueue) != 1) {
					elem7 = q_dequeue(readyQueue);
					my_running_process = elem7;
					printf("\nReadyQ size is: %i", readyQueue->size);
					p_counter = my_running_process->next_step;  //should be new prock;
					//j = my_running_process->request_no;
					//printf("%i", my_running_process->next_step);
				}

				if(keyboardIOQueue->size == 1) {
					pthread_create(&keypth, &attr_detach, keyboardInterrupt, "I'm an arg.");
					//pthread_join(keypth, NULL);
					pthread_detach(diskpth);
				}
				printf("\nPID after KEY_REQ %i\n\n", my_running_process->pid);
				break;

			case IDLE:
				printf( "\n%s%i", "Cycle: ", p_counter);
				printf("\nPID of Running IDLE: %i", my_running_process->pid);
				if(readyQueue->size > 0) {
					qelem_t elem8;
					if (q_isempty(readyQueue) != 1) {
						elem8 = q_dequeue(readyQueue);
						my_running_process = elem8;
						printf("\nReadyQ size is: %i", readyQueue->size);
						p_counter = my_running_process->next_step;  //should be new prock;
						//j = my_running_process->request_no;
						//printf("%i", my_running_process->next_step);
					}
					printf("\nContext Switch!");
					printf("\nPID after IDLE dumped %i", my_running_process->pid);
					p_counter = my_running_process->next_step;
					//j = my_running_process->request_no;
				}
				break;

				int g;
			case MUTEX:
				printf("\nMUTEX");
				//check to see if anyone owns the mutex;
				for(g = 0; g < no_of_processes; g++) {

					if(pcb_list[g].owns == 0 && (pcb_list[g].pid != my_running_process->pid)) {
						q_enqueue(semiphore_queue, (qelem_t) &pcb_list[my_running_process->pid]);
					} else {
						my_running_process->owns = 0; //semi_down_call
					}
				}
				break;

				//MUTEX & SIGNALing
			case SIGNAL_WAIT:  //signal_the buddy:

				//check the conditional variable
				if(my_running_process->owns == 0 && sharedData == 0) {
					my_running_process->waiting_on == sharedData;
					my_running_process->owns == 1; //semi_up_call
				}
				break;


			case SIGNAL:
				printf("\nSIGNAL");
				if(my_running_process->owns == 0) {
					int y;
					for(y = 0; y < no_of_processes; y++) {
						//compare2 = pcb_list[y]->waiting_on;
						//if(compare2 == sharedData) {
						qelem_t elem9;
						if (q_isempty(semiphore_queue) != 1) {
							elem9 = q_dequeue(semiphore_queue);
							PCB_str *keytmp = elem9;
							q_enqueue(readyQueue, (qelem_t) keytmp);
						}
					}
				}

				default:
				printf("Not sure of REQUESTEST.\n");
			}//end switch

			//j++; //increment the_request_placement to get the next request
			//printf("j is %i\n", j);
		}//end if(p_counter == req_num)

		//restart j & next_step so requests will keep happening
		if(p_counter == cpu_cycles -1) {
			my_running_process->next_step = 0; //restart current process
			//j = 0;
		}

		p_counter = p_counter % cpu_cycles;
		p_counter++;
		timer_interrupt_counter--;
		if(p_counter == 1) {
			cycles++;
		}
		//sleep(1);
		//nanosleep(&req, (struct timespec *)NULL);
		int i;
		for(i = 0; i < 100000; i++) {
			printf("");
		}
		//printf("%i ", x);
	}//end while
	//return NULL;
}



/**
 * Create the queues needed by the processes and the PCB list.
 */
void createQueues() {
	readyQueue = q_create();
	readyQueue = malloc(50 * (sizeof(qelem_t) + sizeof(int)));

	diskIOQueue = q_create();
	diskIOQueue = malloc(500 * (sizeof(qelem_t)+ sizeof(int)));
	videoIOQueue = q_create();
	videoIOQueue = malloc(500 * (sizeof(qelem_t)+ sizeof(int)));
	keyboardIOQueue = q_create();
	keyboardIOQueue = malloc(500 * (sizeof(qelem_t)+ sizeof(int)));

	//Interrupt Queue
	interruptQueue = q_create();
	interruptQueue = malloc(1000 * sizeof(int));
	//initBQueue(interruptQueue);
}

int randomRange(int cpu_cycles)
{
	int r;
	//int ratio = cpu_cycles / no_process;
	//srand(time(NULL));
	r = rand() % cpu_cycles;
	//printf("%i\n", r);
	return r;
}

void createProcesses(int cycles, int size, char process_list[]) {
	int pid; //this will also be the process id
	int no_steps;

	for(pid = 0; pid < size; pid++)
	{
		no_steps = randomRange(cycles);
		PCB_str pcb = create_p(no_steps, pid, process_list[pid]);
		pcb_list[pid] = pcb;
		no_steps = 0;
	}

	//Global
	//idle_process = create_p(cycles, size + 1, 'L'); //idle process for readyQ
	PCB_str idle_process = create_p(cycles, size + 1, 'L'); //idle process for readyQ
	pcb_list[size + 1] = idle_process;

	//test:  'p', 'i', 'c', 'i', 'c', 'k', 'p'
	/*int i;
	PCB_str pcb = pcb_list[6];
	int len = pcb.requests_size;
	for(i = 0; i < len; i++) {
		if(pcb.the_requests_placement[i][1] == DISK_REQ) {
			printf("%i %i:  %s\n", i, pcb.the_requests_placement[i][0], "DISK_REQ");
		}

		if(pcb.the_requests_placement[i][1] == VID_REQ) {
			printf("%i %i:  %s\n", i, pcb.the_requests_placement[i][0], "VID_REQ");
		}

		if(pcb.the_requests_placement[i][1] == KEY_REQ) {
			printf("%i %i:  %s\n", i, pcb.the_requests_placement[i][0], "KEY_REQ");
		}

		if(pcb.the_requests_placement[i][1] == MUTEX) {
			printf("%i %i:  %s\n", i, pcb.the_requests_placement[i][0], "MUTEX");
		}
		if(pcb.the_requests_placement[i][1] == SIGNAL_WAIT) {
			printf("%i %i:  %s\n", i, pcb.the_requests_placement[i][0], "SIGNAL_WAIT");
		}

		if(pcb.the_requests_placement[i][1] == SIGNAL) {
			printf("%i %i:  %s\n", i, pcb.the_requests_placement[i][0], "SIGNAL");
		}
	}*/
}

/*
 * Loads ready ready Queue and initializes the running process
 */
void loadReadyQueue(int size)
{
	//printf("%i ", size);
	int i;

	for(i = 0; i < size; i++)
	{
		q_enqueue(readyQueue, (qelem_t) &pcb_list[i]);
	}
	qelem_t elem;
	if (q_isempty(readyQueue) != 1) {
		elem = q_dequeue(readyQueue);
		my_running_process = elem;
		//printf("%i", my_running_process->next_step);
	}
}

//*request_list gen_requests(int no_of_steps, requests[])

int main()
{
	srand(time(NULL));
	//-sim -p 3 -k 3 -c 2 -i 1 -cy 50000
	cpu_cycles = 500000; //cy
	int no_of_processes = 7; //add them all up
	//size = no_processes; //for the size of the pcb list
	char process_list[] = {'c', 'i', 'i', 'c', 'c', 'p', 'k'}; //p,k,c,i else idle

	createQueues();
	//interuptbuffer initializer
	tail = 0;
	head = MAX;

	createProcesses(cpu_cycles, no_of_processes, process_list); //change later
	loadReadyQueue(no_of_processes);

	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

	pthread_attr_init(&attr_detach);
	pthread_attr_setdetachstate(&attr_detach, PTHREAD_CREATE_DETACHED);

	pthread_mutex_init(&mutex, NULL);
	pthread_mutex_init(&qlock, NULL);
	pthread_cond_init(&buf_not_full, NULL);
	pthread_cond_init(&buf_not_empty, NULL);

	//start main loop
	cpuCounter(/*cpu_cycles*/);



	pthread_attr_destroy(&attr);
	pthread_mutex_destroy(&mutex);
	pthread_mutex_destroy(&qlock);
	pthread_cond_destroy(&buf_not_full);
	pthread_cond_destroy(&buf_not_empty);
	pthread_exit(NULL);

	return 0;
}

